#include "LogUtils.h"

#include <windows.h>
#include <map>
#include <time.h>
#include <algorithm>

namespace // private namespace
{
	enum Colours
	{
		FG_BLUE  = 0x0001,	// Static constant integers for changing the
		FG_GREEN = 0x0002,	// colour of the formatting printing, kept in a
		FG_RED   = 0x0004,	// private namespace to contain them to just the
		FG_INTST = 0x0008	// functions within this file.
	};

	/* formattedPrint():
	 *		Prints a formatted message to the output stream,
	 *		consisting of prefix - fmt - suffix. Wrapped in
	 *		a private namespace so only methods in this file
	 *		can call it.
	 */
	void formattedPrint(FILE* output, const char* prefix, const char* suffix, const char* fmt, va_list args)
	{
		fprintf(output, prefix);
		vfprintf(output, fmt, args);
		fprintf(output, suffix);
	}

	/* resetConsoleColours():
	 *		Resets the console print colour to white on black.
	 *		Wrapped in a private namespace so only methods in
	 *		this file can call it.
	 */
	void resetConsoleColours()
	{
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (FG_RED | FG_GREEN | FG_BLUE));
	}
}

/* log():
 *		Prints the formatted message to a given logfile,
 *		which will have the timestamp the logfile was made
 *		appended to it.
 */
void Log::LogToFile(const char* filename, const char* fmt, ...)
{
	static std::map<const char*, FILE*> logfiles;

	// If the log file doesn't exist (in the LogUtils system) then create it.
	if (logfiles.find(filename) == logfiles.end())
	{
		const int SIZE = 26;
		const char* START = " (", *END = ").log";

		// Get a timestamp and convert it into a local-time string format.
		time_t rawtime = time(nullptr);
		char timeline[SIZE];
		ctime_s(timeline, SIZE, &rawtime);

		// Create the start of the full file name.
		std::string fullFileName(filename);
		
		// Append the START value and the timestamp.
		fullFileName += START;
		fullFileName += timeline;

		// Remove the newline character appended by the timestamp, and
		// replace the colons (invalid filename characters) with dashes.
		fullFileName = fullFileName.substr(0, fullFileName.length() - 1);
		std::replace(fullFileName.begin(), fullFileName.end(), ':', '-');
		
		// append the END value to the full file name.
		fullFileName += END;
		
		// open the file in append mode, but if that doesn't work for whatever
		// reason then we need to remove it from the mapping and return.
		if (fopen_s(&logfiles[filename], fullFileName.c_str(), "a") != 0)
		{
			Error("Unable to open a logfile with name %s!", filename);
			logfiles.erase(filename);
			return;
		}
		
		// Set the console to green and print that a new log file has been created.
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (FG_GREEN | FG_INTST));
		Print("Created new log file: %s", fullFileName.c_str());

		// Reset the console colours.
		resetConsoleColours();
	}

	va_list args;
	va_start(args, fmt);
	formattedPrint(logfiles[filename], "", "\n", fmt, args);
	va_end(args);

	// flush the logfile, because if the program crashes there is a chance
	// not all the data will make it into the log file, which could be
	// counterproductive if the log regards the crash data!
	fflush(logfiles[filename]);
}

/* print():
 *		Prints a message to the std::out stream,
 *		appended with a newline character.
 */
void Log::Print(const char* fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	formattedPrint(stdout, "", "\n", fmt, args);
	va_end(args);
}

/* printGreen():
 *		Prints a message to the std::out stream,
 *		appended with a newline character. The message
 *		is printed in bold green.
 */
void Log::PrintGreen(const char* fmt, ...)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (FG_GREEN | FG_INTST));

	va_list args;
	va_start(args, fmt);
	formattedPrint(stdout, "", "\n", fmt, args);
	va_end(args);

	resetConsoleColours();
}

/* debug():
 *		Prints a message to the std::out stream, prefixed
 *		with "Debug: ". Message is printed in cyan to stand
 *		out, and is appended with a newline character.
 */
void Log::Debug(const char* fmt, ...)
{
	// Set the print colour for the console to cyan on black.
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (FG_GREEN | FG_BLUE | FG_INTST));

	va_list args;
	va_start(args, fmt);
	formattedPrint(stdout, "Debug: ", "\n", fmt, args);
	va_end(args);

	// Reset the console colours.
	resetConsoleColours();
}

/* error():
 *		Prints a message to the std::err stream prefixed
 *		with "Error: ". Message is printed in red to stand
 *		out, and is appended with a newline character.
 */
void Log::Error(const char* fmt, ...)
{
	// Set the print colour for the console to red on black.
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (FG_RED | FG_INTST));
	
	va_list args;
	va_start(args, fmt);
	formattedPrint(stderr, "Error: ", "\n", fmt, args);
	va_end(args);

	// Reset the console colours.
	resetConsoleColours();
}

/* warning():
 *		Prints a message to the std::out stream prefixed
 *		with "Warning: ". Message is printed in yellow to stand
 *		out, and is appended with a newline character.
 */
void Log::Warning(const char* fmt, ...)
{
	// Set the print colour for the console to yellow on black.
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), (FG_RED | FG_GREEN | FG_INTST));

	va_list args;
	va_start(args, fmt);
	formattedPrint(stdout, "Warning: ", "\n", fmt, args);
	va_end(args);

	// Reset the console colours.
	resetConsoleColours();
}